package io.vividcode.happyride.driversimulator.web;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vividcode.happyride.driversimulator.DriverSimulator;
import io.vividcode.happyride.driversimulator.DriverSimulatorRegistry;
import io.vividcode.happyride.driversimulator.DriverSimulatorSnapshot;

import java.net.URI;
import java.time.Duration;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/api")
public class DriverSimulatorController {

    @Autowired
    DriverSimulatorRegistry driverSimulatorRegistry;

    private final AtomicInteger driverId = new AtomicInteger(1);
    private final ObjectMapper objectMapper = new ObjectMapper();

    @GetMapping
    public Flux<DriverSimulatorSnapshot> list() {
        return Flux.fromIterable(this.getDrivers());
    }

    @GetMapping(value = "live", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<ServerSentEvent<String>> live() {
        return Flux.interval(Duration.ofSeconds(5))
                .map(sequence -> ServerSentEvent.<String>builder()
                        .id(String.valueOf(sequence))
                        .event("driverUpdated")
                        .data(this.getDriversJsonData())
                        .build());
    }

    private List<DriverSimulatorSnapshot> getDrivers() {
        return this.driverSimulatorRegistry.list().stream()
                .sorted(Comparator.comparing(DriverSimulator::getDriverId))
                .map(DriverSimulator::dump)
                .collect(Collectors.toList());
    }

    private String getDriversJsonData() {
        try {
            return this.objectMapper.writeValueAsString(this.getDrivers());
        } catch (final JsonProcessingException e) {
            return "[]";
        }
    }

    @GetMapping("{id}")
    public ResponseEntity<DriverSimulatorSnapshot> getDriverSimulator(
            @PathVariable("id") final String driverSimulatorId) {
        return Optional
                .ofNullable(this.driverSimulatorRegistry.get(driverSimulatorId))
                .map(simulator -> ResponseEntity.ok(simulator.dump()))
                .orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PostMapping("/")
    public ResponseEntity<Void> addDriver(
            @RequestBody final AddDriverRequest request,
            final ServerHttpRequest httpRequest) {
        final DriverSimulator simulator = this.driverSimulatorRegistry.add(request);
        return ResponseEntity
                .created(this.resourceCreated(httpRequest, simulator.getId()))
                .build();
    }

    @PostMapping("quickAdd")
    public ResponseEntity<Void> quickAddDriver(
            final ServerHttpRequest httpRequest) {
        return this.addDriver(new AddDriverRequest(
                        String.format("Driver-%02d", this.driverId.getAndIncrement())),
                httpRequest);
    }

    @PostMapping("{id}/start")
    public void startSimulation(
            @PathVariable("id") final String driverSimulatorId) {
        this.driverSimulatorRegistry.start(driverSimulatorId);
    }

    @PostMapping("{id}/stop")
    public void stopSimulation(
            @PathVariable("id") final String driverSimulatorId) {
        this.driverSimulatorRegistry.stop(driverSimulatorId);
    }

    @PostMapping("{id}/resetPosition")
    public void resetPosition(@PathVariable("id") final String driverSimulatorId,
                              @RequestBody final ResetPositionRequest request) {
        this.driverSimulatorRegistry
                .resetPosition(driverSimulatorId, request.getPosLng(),
                        request.getPosLat());
    }

    @PostMapping("{id}/markAsAvailable")
    public void markAsAvailable(
            @PathVariable("id") final String driverSimulatorId) {
        this.driverSimulatorRegistry.markAsAvailable(driverSimulatorId);
    }

    @PostMapping("{id}/markAsNotAvailable")
    public void markAsNotAvailable(
            @PathVariable("id") final String driverSimulatorId) {
        this.driverSimulatorRegistry.markAsNotAvailable(driverSimulatorId);
    }

    private URI resourceCreated(final HttpRequest request,
                                final String resourceId) {
        return UriComponentsBuilder.fromHttpRequest(request)
                .replacePath("/{id}")
                .buildAndExpand(resourceId)
                .toUri();
    }
}
